//@ts-nocheck
import {
  ELEMENT_TEXT,
  TAG_HOST,
  TAG_ROOT,
  TAG_TEXT,
  PLACEMENT,
  UPDATE,
  DELETION,
  TAG_CLASS,
  TAG_FUNCTION_COMPONENT
} from './constants';
import {setProps} from './util';
import {UpdateQueue,Update} from './UpdateQueue';

/** 从根节点开始渲染和调度
 每次渲染有两个阶段：
 + Reconciliation(协调\render阶段)
 + Commit(提交阶段)

 协调阶段: 可以认为是 Diff 阶段, 这个阶段可以被中断(暂停), 这个阶段会找出所有节点变更，例如节点新增、删除、属性变更等等, 这些变更React 称之为副作用(Effect)

 提交阶段: 将上一个阶段计算出来的需要处理的副作用(Effects)一次性执行了。这个阶段必须同步执行，不能被打断
 */
let nextUnitOfWork = null; /**←下一个工作单元 只要nextUnitOfWork有值, 在下一次idleCallback得以执行时就会被自动执行 参看doc/life-of-frame.png*/
let workInProgressRoot = null; // 正在渲染的根ROOT fiber
let currentRoot = null; // 渲染成功之后当前根ROOT Fiber 即当前看到的页面对应的ROOT Fiber
const deletions = []; //删除的节点我们并不放在effect list里, 所以需要单独记录并执行
let workInProgressFiber = null; // 正在工作中的fiber
let hookIndex = 0; // hooks索引

// ReactDOM.render被调用时会调用↓
export function scheduleRoot(rootFiber){ // {tag:TAG_ROOT,stateNode:container,props:{children:[element]}}
  // 第三次渲染
  if (currentRoot && currentRoot.alternate) {
    workInProgressRoot = currentRoot.alternate; // 第一次渲染的那颗树
    if(rootFiber) workInProgressRoot.props = rootFiber.props/**←这里的props就是虚拟dom的props,标签属性、children撒的*/; // 让它的props更新成新的props
    workInProgressRoot.alternate = currentRoot; // 让这个树的替身指向当前页面对应的fiber树


  // 说明至少已经渲染过一次 第一次更新
  } else if (currentRoot) {
    if(rootFiber){
      rootFiber.alternate = currentRoot; // alternate 替身的意思 指向上次一次渲染的fiber树
      workInProgressRoot = rootFiber; // ←方便找到根 每次执行不同小任务时不会变
    }else{ // ← 比如类组件更新(setState) 会走这里
      workInProgressRoot = {
        ...currentRoot,
        alternate: currentRoot
      }
    }


  // 第一次渲染
  } else {
    workInProgressRoot = rootFiber; // ←方便找到根 每次执行不同小任务时不会变
  }

  workInProgressRoot.firstEffect = workInProgressRoot.lastEffect = workInProgressRoot.nextEffect = null;

  /** ↓此时nextUnitOfWork有值了, 会在下一次idleCallback得以执行时就会被自动执行*/
  nextUnitOfWork = workInProgressRoot; // ←每次执行不同小任务时都会变 或则说 这就代表不同小任务
}

// react告诉浏览器 我现在有任务请你在闲的时候执行
// 当前文件(schedule.tsx)被引入时 就开始不停进行
/** 轮询*/
requestIdleCallback(workLoop, {timeout: 500/*←我们先这么写 其实这里 有一个优先级的概念 expirationTIme*/});

// 循环执行工作 nextUnitWork
function workLoop(deadline){
  let shouldYield = false; // 是否让出时间片或则说控制权
  while (nextUnitOfWork && !shouldYield){
    /** 阶段一: 协调阶段
     * 1. 构建fiber链表
     * 2. 创建并绑定真实dom
     * 3. 生成effect链表
     * */
    nextUnitOfWork/**←当读取到rootFiber.return时 这就没有返回值了 就会走到commitRoot里*/ = performUnitOfWork(nextUnitOfWork);
    shouldYield = deadline.timeRemaining() < 1; // 剩余时间不够了 让出控制权
  } // 走到这里

  if(!nextUnitOfWork && workInProgressRoot){
    console.log('render阶段结束');
    /** 阶段二: 提交阶段:
     * + 将上一个阶段计算出来的需要处理的副作用(Effects)一次性执行了。
     * + 这个阶段必须同步执行，不能被打断*/
    commitRoot();
  }

  //不管有没有任务 都请求再次调度 每一帧都要执行一次workLoop
  requestIdleCallback(workLoop, {timeout: 500});
}

function commitRoot(){
  deletions.forEach(commitWork); // 执行effect list之前先把要删除的元素删除

  let currentFiber = workInProgressRoot.firstEffect;
  while (currentFiber){
    commitWork(currentFiber);
    currentFiber = currentFiber.nextEffect;
  }

  deletions.length = [];

  currentRoot = workInProgressRoot; //把当前渲染成功的根fiber 赋给currentRoot
  workInProgressRoot = null;
}

function commitWork(currentFiber) {
  if (!currentFiber) return;
  let returnFiber = currentFiber.return;

  //↓直到找到真实dom位置(因为可能是个组件
  while (returnFiber.tag !== TAG_HOST && returnFiber.tag !== TAG_ROOT && returnFiber.tag !== TAG_TEXT) {
    returnFiber = returnFiber.return;
  }

  const returnDOM = returnFiber.stateNode;

  if (currentFiber.effectTag === PLACEMENT) { // 新增
    let nextFiber = currentFiber;

    // 因为class组件 和 class组件对应的真实dom节点是两个不同的fiber节点(class组件它对应真实dom节点的fiber节点 是 class组件fiber节点的child)
    // 而class组件自己这个fiber节点显然不需要挂载
    if(nextFiber.tag === TAG_CLASS){
      return
    }

    // 如果要挂载的节点不是DOM节点 比如说是类组件Fiber 就要往下找 直到找到一个真实DOM节点位置
    while (nextFiber.tag !== TAG_HOST && nextFiber.tag !== TAG_ROOT && nextFiber.tag !== TAG_TEXT) {
      nextFiber = currentFiber.child;
    }
    returnDOM.appendChild(nextFiber.stateNode);

  }else if(currentFiber.effectTag === DELETION){ // 删除节点
    // returnDOM.removeChild(currentFiber.stateNode)
    commitDeletion(currentFiber, returnDOM);

  }else if(currentFiber.effectTag === UPDATE){
    if(currentFiber.type === ELEMENT_TEXT){
      if(currentFiber.alternate/*←上一次的fiber*/.props.text != currentFiber.props.text){
        currentFiber.stateNode.textContent = currentFiber.props.text
      }
    }else {
      if (currentFiber.tag === TAG_CLASS || currentFiber.tag === TAG_FUNCTION_COMPONENT) {
        return currentFiber.effectTag = null;
      }
      updateDOM(currentFiber.stateNode,currentFiber.alternate.props,currentFiber.props)
    }
  }
  currentFiber.effectTag = null;
}

function commitDeletion(currentFiber, returnDOM){
  if(currentFiber.tag === TAG_HOST || currentFiber.tag === TAG_TEXT){
    returnDOM.removeChild(currentFiber.stateNode);
  }else {
    commitDeletion(currentFiber.child, returnDOM);
  }
}

/**
 * 1. 构建fiber链表
 * 2. 创建并绑定真实dom
 * 3. 生成effect链表
 *
 * 一次只会处理一个fiber节点,
 * 但会将fiber树往下构建一层(连通.child、.sibling(孩子间)),下一次执行 就是执行它的孩子
 * 并为子fiber设置effectTag (PLACEMENT(新增)、UPDATE、DELETION，以供commit阶段打补丁),
 * 也就是所谓的调和
 *
 * 另外如果这个fiber节点没有孩子了, 则会completeUnitOfWork
 * 即构建effectList, 以供commit阶段使用
 * */
function performUnitOfWork(currentFiber){
  beginWork(currentFiber);
  /*
    开始收下线的钱 往下构建一层fiber链(每个fiber节点 带有自身的effect描述标签effectTag)
      A
    B1 B2
  */


  if(currentFiber.child){ //B1
    return currentFiber.child;
  }

  while (currentFiber){
    completeUnitOfWork(currentFiber); // C1 // C2 B1
    /*检查是否有effectTag 有则挂在由nextEffect属性连接的fiber链表上
      B1.nextEffect = C1
      C1.nextEffect = C2
    * */

    if(currentFiber.sibling){ // C1.sibling -> C2
      return currentFiber.sibling;
    }
    currentFiber = currentFiber.return; // C2.return
  }
}

function beginWork(currentFiber){
  if(currentFiber.tag === TAG_ROOT) {
    updateHostRoot(currentFiber); // host主人的意思
  }else if(currentFiber.tag === TAG_TEXT){
    updateHostText(currentFiber);
  }else if(currentFiber.tag === TAG_HOST){
    updateHost(currentFiber);
  }else if(currentFiber.tag === TAG_CLASS){
    updateClassComponent(currentFiber);
  }else if(currentFiber.tag === TAG_FUNCTION_COMPONENT){
    updateFunctionComponent(currentFiber);
  }
}

function updateFunctionComponent(currentFiber){
  workInProgressFiber = currentFiber;
  hookIndex = 0;
  workInProgressFiber.hooks = [];
  const newChildren = [currentFiber.type(currentFiber.props)];
  reconcileChildren(currentFiber, newChildren);
}

function updateClassComponent(currentFiber) {
  if(!currentFiber.stateNode){ // 类组件 stateNode 组件的实例
    currentFiber.stateNode = new currentFiber.type(currentFiber.props);
    currentFiber.stateNode.internalFiber = currentFiber; // 类组件实例 fiber双向指向
    currentFiber.updateQueue = new UpdateQueue();
  }

  currentFiber.stateNode.state/*←更新组件的实例的state*/ = currentFiber.updateQueue.forceUpdate(currentFiber.stateNode.state/*老状态传过去给setState的函数形式使用*/);
  const newElement = currentFiber.stateNode.render();
  const newChildren = [newElement];
  reconcileChildren(currentFiber, newChildren);
}

function updateHost(currentFiber){
  if (!currentFiber.stateNode) {
    currentFiber.stateNode = createDOM(currentFiber);
  }

  const newChildren = currentFiber.props.children;
  reconcileChildren(currentFiber,newChildren)
}

function createDOM(currentFiber){
  if(currentFiber.tag === TAG_TEXT){
    return document.createTextNode(currentFiber.props.text);
  }else if (currentFiber.tag === TAG_HOST){ // span div
    const stateNode = document.createElement(currentFiber.type); // div
    updateDOM(stateNode, {}, currentFiber.props);
    return stateNode;
  }
}

function updateDOM(stateNode, oldProps, newProps) {
  if(stateNode && stateNode.setAttribute){
    setProps(stateNode, oldProps, newProps);
  }
}

function updateHostText(currentFiber){
  if(!currentFiber.stateNode){
    currentFiber.stateNode = createDOM(currentFiber);
  }
}

function updateHostRoot(currentFiber){
  // 先处理自己 如果是一个原生节点的话 创建真实DOM
  const newChildren = currentFiber.props.children;
  reconcileChildren(currentFiber, newChildren)
}

/**
 * 1. 虚拟dom节点 -> fiber节点
 * 2. 构建fiber链(调用一次reconcileChildren只会针对该节点往下构建一层)
 * 3. 设置子fiber的effectTag (PLACEMENT(新增)、UPDATE、DELETION，以供commit阶段打补丁
 * */
function reconcileChildren(currentFiber, newChildren/*←虚拟dom数组*/) {
  let newChildIndex = 0; // 新子节点的索引
  let prevSibling; // 上一个子fiber
  let oldChildFiber = currentFiber.alternate && currentFiber.alternate.child;
  if(oldChildFiber) oldChildFiber.firstEffect = oldChildFiber.lastEffect = oldChildFiber.nextEffect = null;

  // 遍历我们的子虚拟DOM元素数组 为每个虚拟DOM元素创建fiber
  while (newChildIndex < newChildren.length || oldChildFiber/**←新的索引没有越界 或则 老的还有*/) {
    const newChild = newChildren[newChildIndex]; // 取出虚拟DOM节点
    let nextFiber;
    const sameType = oldChildFiber && newChild && oldChildFiber.type === newChild.type;

    let tag;
    if (newChild) {
      if (typeof newChild.type === 'function' && newChild.type.prototype.isReactComponent) {
        tag = TAG_CLASS
      } else if (newChild.type == ELEMENT_TEXT/*←这个是我们在createElement方法中自己处理的*/) {
        tag = TAG_TEXT; // 这是一个文本节点
      } else if (typeof newChild.type === 'string') {
        tag = TAG_HOST; // 如果type是字符串 那么这是一个原生DOM节点
      }else if (typeof newChild.type === 'function') {
        tag = TAG_FUNCTION_COMPONENT; // 如果type是字符串 那么这是一个原生DOM节点
      }
    }

    if (sameType) { // 说明老fiber和新虚拟dom类型一样 可以复用老的DOM节点 更新即可
      if (oldChildFiber.alternate) { // 说明至少已经更新过一次了 这是第三次渲染
        nextFiber = oldChildFiber.alternate; //todo emmm so 这样复用就只是为了 省那一次 newFiber = {} 对象的创建？
        nextFiber.props = newChild.props;
        nextFiber.alternate = oldChildFiber;
        nextFiber.effectTag = UPDATE;
        nextFiber.updateQueue = oldChildFiber.updateQueue || new UpdateQueue();
        nextFiber.nextEffect = null;
      } else {
        nextFiber = {
          tag: oldChildFiber.tag, // ←表名fiber节点的tag类型 TAG_ROOT TAG_HOST TAG_TEXT
          type: oldChildFiber.type, // div span...  function , class(其实也是个function)
          props: newChild.props, /** 一定要用新的元素的props*/
          stateNode: oldChildFiber.stateNode, // 还没有对应的真实dom元素
          return: currentFiber, // 父Fiber returnFiber
          updateQueue: oldChildFiber.updateQueue || new UpdateQueue(),
          alternate: oldChildFiber, /** ←指向老的fiber节点*/
          effectTag: UPDATE, // 副作用标识 render阶段我们会收集副作用 增加 删除 更新
          nextEffect: null // effect也是一个单链表 顺序和我们遍历fiber节点的完成顺序是一样的(不出钱 会跳过)
        }
      }

    } else { // 第一次走这里
      if (newChild) { // 看看新的虚拟dom是不是为null 比如删除了某个节点
        nextFiber = {
          tag, // ←表名fiber节点的tag类型 TAG_ROOT TAG_HOST TAG_TEXT
          type: newChild.type, // div span...  function , class(其实也是个function)
          props: newChild.props,
          stateNode: null, // 还没有对应的真实dom元素
          return: currentFiber, // 父Fiber returnFiber
          effectTag: PLACEMENT, // 副作用标识 render阶段我们会收集副作用 增加 删除 更新
          updateQueue: new UpdateQueue(),
          nextEffect: null // effect也是一个单链表 顺序和我们遍历fiber节点的完成顺序是一样的(不出钱 会跳过)
        };
      }
      if (oldChildFiber) { // 因为类型不一样 so 需要将老的删掉
        oldChildFiber.effectTag = DELETION;
        deletions.push(oldChildFiber)
      }
    }

    if (oldChildFiber) {
      //todo 我们这里没有按key比较 是一一对比
      oldChildFiber = oldChildFiber.sibling;
    }


    /** ↓构建fiber链
     * 注意,这里只会往下构建一层
     * */
    if (nextFiber) {
      if (newChildIndex == 0) { // 如果当前索引为0, 说明是第一个儿子 是太子
        currentFiber.child = nextFiber; /** 此时就有.child了 .child会作为下一次的nextUnitOfWork*/
      } else {
        prevSibling.sibling = nextFiber; // 让太子的sibling 指向二皇子
      }
      prevSibling = nextFiber; // 记录本次的fiber 以助于下次循环(说明是有弟弟的情况) 将弟弟挂在这次记录的fiber的.sibling属性上 形成链条
    }
    newChildIndex++;
  }
}

// 在完成的时候要收集有副作用的fiber 然后组成effect list
// 每个fiber有两个属性 firstEffect指向第一个有副作用的子fiber
// lastEffect 指向最后一个有副作用的子fiber
/*
      A
  B1     B2
C1  C2 D1  D2

收集到的effect链是这样的 → C1 C2 B1 D1 D2 B2
和完成链的顺序是一样的(只是没有副作用的不会挂上去)

参看 effect链接.png
*/
function completeUnitOfWork(currentFiber) {
  const returnFiber = currentFiber.return; //B1 //B1 A
  if (returnFiber) {
    //先把自己儿子的effect挂到父亲身上 start ---
    //#region
    if (!returnFiber.firstEffect) { // ←这块代码貌似是多余的 参看下面if(effectTag)里的else分支 但其实不是 因为可能一个fiber节点自身并没有副作用 但它的子节点有(这里说的子节点,相对于returnFiber节点,跨了两层,即孙子节点)
      returnFiber.firstEffect = currentFiber.firstEffect; // A.firstEffect = B1.firstEffect = C1
    }

    if (currentFiber.lastEffect) {
      if(returnFiber.lastEffect){
        returnFiber.lastEffect.nextEffect = currentFiber.firstEffect; // A.lastEffect.nextEffect = B1.firstEffect = C1
      }
      returnFiber.lastEffect = currentFiber.lastEffect; // // A.lastEffect = B1.lastEffect = C2
    }
    //#endregion
    //--- 把自己儿子的effect挂到父亲身上 end


    //把自己挂到父亲身上 start ---
    //#region
    const effectTag = currentFiber.effectTag;

    /** 有effect才会往effect链中记录 否则会略过此节点*/
    /** ↓第一次进来走这里*/
    if (effectTag) { // 自己有副作用
      if (returnFiber.lastEffect) {
        returnFiber.lastEffect.nextEffect = currentFiber; // B1.lastEffect.nextEffect = C2, 即C1.nextEffect = C2  // A.lastEffect.nextEffect = B1
      }else{ /**←第一次进来走这里*/
        returnFiber.firstEffect = currentFiber; // B1.firstEffect = C1
      }
      returnFiber.lastEffect = currentFiber; // B1.lastEffect = C1 // B1.lastEffect = C2 // A.lastEffect = B1
    }
    //#endregion
    //--- 把自己挂到父亲身上 end

  }
}

export function useReducer(reducer, initialValue) {
  const oldHook = workInProgressFiber.alternate && workInProgressFiber.alternate.hooks && workInProgressFiber.alternate.hooks[hookIndex];
  let newHook = oldHook;

  if(oldHook){ // 第二次渲染
    oldHook.state = oldHook.updateQueue.forceUpdate(oldHook.state);
  }else {
    newHook = {
      state: initialValue,
      updateQueue: new UpdateQueue()
    }
  }

  const dispatch = action => { // {type:"ADD"}
    const payload = reducer ? reducer(newHook.state, action) : action/**←useState时有用*/;
    newHook.updateQueue.enqueueUpdate(new Update(payload));
    scheduleRoot();
  };

  workInProgressFiber.hooks[hookIndex++] = newHook;
  return [newHook.state, dispatch];
}

export function useState(initialValue){
  return useReducer(null, initialValue);
}
